﻿using ArtMath.Resource;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace ArtMath.Core.Helper
{
    /// <summary>
    /// 计算类（由于计算对性能要求严苛，所以此类的所有自定义函数都不会抛异常）
    /// </summary>
    public static class Calculation
    {
        /// <summary>
        /// 不含变量的计算函数（只允许表达式有函数和常数的四则运算，例如(sin(π)+1)*e）
        /// </summary>
        public static bool Calculate(string exp, out double result)
        {
            if (!string.IsNullOrEmpty(exp))
            {
                exp = Utility.StandardizePolynomial(exp);
                if (replaceFunction(ref exp))
                {
                    exp = Utility.StandardizePolynomial(exp);
                    if (calc(exp, out result))
                    {
                        return true;
                    }
                }
            }
            result = 0;
            return false;// throw new ArgumentException("EmptyExpression");
        }
        //public static bool IsValidExpression(string exp, params double[] invalidValues)
        //{
        //    double res;
        //    try
        //    {
        //        res = Calculate(exp);
        //    }
        //    catch (Exception)
        //    {
        //        return false;
        //    }
        //    if (invalidValues == null || invalidValues.Length == 0) return true;
        //    return Array.BinarySearch(invalidValues, res) < 0;
        //}
        static bool calc(string exp, out double result)
        {
            //if (string.IsNullOrEmpty(exp)) return 0;
            StringBuilder sb = new StringBuilder(exp);
            bool isPercentage = sb[sb.Length - 1] == '%';
            if (isPercentage)
            {
                sb.Remove(sb.Length - 1, 1);
            }
            //double result;
            if (!StringToDouble(sb.ToString(), out result))
            {
                if (sb[0] == '-') sb.Insert(0, '0');
                sb.Append('#');
                //分割表达式
                Queue<string> q = new Queue<string>();
                int i = 0;
                for (int j = 0; j < sb.Length; j++)
                {
                    if (IsOperator(sb[j].ToString()))
                    {
                        if (i == j)
                        {
                            if (!Utility.IsSign(sb, j))//正负号不添加
                            {
                                q.Enqueue(sb[j].ToString());
                                i++;
                            }
                        }
                        else
                        {
                            q.Enqueue(sb.ToString(i, j - i));
                            q.Enqueue(sb[j].ToString());
                            i = j + 1;
                        }
                    }
                }
                //中序转后序
                List<string> posterOrder = new List<string>();
                Stack<string> inOrder = new Stack<string>();
                inOrder.Push("#");
                int count = q.Count;
                for (i = 0; i < count; i++)
                {
                    string item = q.Dequeue();
                    if (IsOperator(item))
                    {
                        char chr;
                        while ((chr = IsPriority(inOrder.Peek(), item)) == '>')
                        {
                            string temp = inOrder.Pop();
                            if (temp != "(" && temp != ")")
                            {
                                posterOrder.Add(temp);
                            }
                        }
                        switch (chr)
                        {
                            case '=':
                                inOrder.Pop();
                                break;
                            case '?':
                                result = double.NaN;
                                return false;
                                //throw new ArithmeticException(Resources.Translate("CalculationFailed"));
                            default:
                                inOrder.Push(item);
                                break;
                        }
                    }
                    else
                    {
                        posterOrder.Add(item);
                    }
                }
                if (inOrder.Count > 0)
                {
                    result = double.NaN;
                    return false;
                    //throw new ArithmeticException(Resources.Translate("CalculationFailed"));
                }
                posterOrder.Add("#");
                for (i = 0; i < posterOrder.Count && posterOrder[i] != "#"; i++)
                {
                    if (IsOperator(posterOrder[i]))
                    {
                        if (!StringToDouble(posterOrder[i - 2], out double lp) || !StringToDouble(posterOrder[i - 1], out double rp))
                        {
                            return false;
                        }
                        switch (posterOrder[i])
                        {
                            case "+":
                                posterOrder[i - 2] = (lp + rp).ToString();
                                break;
                            case "-":
                                posterOrder[i - 2] = (lp - rp).ToString();
                                break;
                            case "*":
                                posterOrder[i - 2] = (lp * rp).ToString();
                                break;
                            case "/":
                                posterOrder[i - 2] = (lp / rp).ToString();
                                break;
                            case "^":
                                posterOrder[i - 2] = Math.Pow(lp, rp).ToString();
                                break;
                        }
                        posterOrder.RemoveAt(i);
                        posterOrder.RemoveAt(i - 1);
                        i -= 2;
                    }
                }
                if (!StringToDouble(posterOrder[0], out result))
                {
                    return false;
                }
            }
            if (isPercentage) result /= 100;
            return true;
        }
        public static bool StringToDouble(string exp, out double result)
        {
            if (exp.Equals("e", StringComparison.OrdinalIgnoreCase))
            {
                result = Math.E;
            }
            else if (exp == "π" || exp.Equals("pi", StringComparison.OrdinalIgnoreCase))
            {
                result = Math.PI;
            }
            else
            {
                return double.TryParse(exp, out result);
            }
            return true;
        }
        static bool replaceFunction(ref string exp)
        {
            Stack<KeyValuePair<string, int>> func = new Stack<KeyValuePair<string, int>>();
            StringBuilder funcName = new StringBuilder();
            for (int i = 0; i < exp.Length; i++)
            {
                if (char.IsLetter(exp[i]))
                {
                    funcName.Append(exp[i]);
                }
                else
                {
                    switch (exp[i])
                    {
                        case '+':
                        case '-':
                        case '*':
                        case '/':
                        case '^':
                            funcName.Clear();
                            break;
                        case '(':
                            func.Push(new KeyValuePair<string, int>(funcName.ToString(), i));
                            funcName.Clear();
                            break;
                        case ')':
                            KeyValuePair<string, int> lbrk = func.Pop();
                            if (string.IsNullOrEmpty(lbrk.Key)) continue;
                            string[] exps = exp.Substring(lbrk.Value + 1, i - lbrk.Value - 1).Split(',');
                            double[] args = new double[exps.Length];
                            for (int j = 0; j < exps.Length; j++)
                            {
                                int expLastIndex = exps[j].Length - 1;
                                bool degreeFlag = false;
                                if ((lbrk.Key.Equals("sin", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("cos", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("tan", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("sinh", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("cosh", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("tanh", StringComparison.OrdinalIgnoreCase)) &&
                                    (exps[j][expLastIndex] == 'd' || exps[j][expLastIndex] == 'D') &&
                                    (char.IsDigit(exps[j][expLastIndex - 1]) || exps[j][expLastIndex - 1] == ')' || exps[j][expLastIndex - 1] == '%'))
                                {
                                    degreeFlag = true;
                                    expLastIndex--;
                                }
                                bool isPercentage = exp[expLastIndex] == '%';
                                if (isPercentage) expLastIndex--;
                                if (!calc(exps[j].Substring(0, expLastIndex + 1), out args[j]))
                                {
                                    return false;
                                }
                                if (isPercentage) args[j] /= 100;
                                if (degreeFlag) args[j] *= Angle.DToR;
                            }
                            double funcVal = funcCalc(lbrk.Key?.ToLower(), args);
                            if (double.IsNaN(funcVal))
                            {
                                return false;
                                //throw new NotFiniteNumberException(Resources.Translate("NaNCalculation"));
                            }
                            string oldExp = exp.Substring(lbrk.Value - lbrk.Key.Length, i - lbrk.Value + lbrk.Key.Length + 1), newExp = $"({funcVal})";
                            exp = exp.Replace(oldExp, newExp);
                            i += newExp.Length - oldExp.Length;
                            break;
                    }
                }
            }
            return true;
        }
        static double funcCalc(string funcName, params double[] args)
        {
            if (funcName == null)
            {
                return args[0];
            }
            else
            {
                switch (funcName)
                {
                    case "sin": return Math.Sin(args[0]);
                    case "cos": return Math.Cos(args[0]);
                    case "tan":
                    case "tg":
                        return Math.Tan(args[0]);
                    case "cot":
                    case "ctg":
                        return 1 / Math.Tan(args[0]);
                    case "csc": return 1 / Math.Sin(args[0]);
                    case "sec": return 1 / Math.Cos(args[0]);
                    case "sinh": return Math.Sinh(args[0]);
                    case "cosh": return Math.Cosh(args[0]);
                    case "tanh": return Math.Tanh(args[0]);
                    case "arctan":
                    case "atan":
                        return Math.Atan(args[0]);
                    case "arccos":
                    case "acos":
                        return Math.Acos(args[0]);
                    case "arcsin":
                    case "asin":
                        return Math.Asin(args[0]);
                    case "arccsc":
                    case "acsc":
                        return Math.Asin(1 / args[0]);
                    case "arcsec":
                    case "asec":
                        return Math.Acos(1 / args[0]);
                    case "arccot":
                    case "arcctg":
                    case "acot":
                    case "actg":
                        return Math.Atan(1 / args[0]);
                    case "abs": return Math.Abs(args[0]);
                    case "ln": return Math.Log(args[0]);
                    case "lg": return Math.Log10(args[0]);
                    case "log": return Math.Log(args[1], args[0]);
                    case "sqrt": return Math.Sqrt(args[0]);
                    case "cbrt": return Math.Sign(args[0]) * Math.Pow(Math.Abs(args[0]), 1.0 / 3);//有bug
                    case "floor": return Math.Floor(args[0]);
                    case "ceil": return Math.Ceiling(args[0]);
                    case "trunc": return Math.Truncate(args[0]);
                    case "pow":
                        return Math.Pow(args[0], args[1]);
                    case "rt":
                        return Math.Pow(args[1], 1 / args[0]);
                    case "mod": return args[0] % args[1];
                    case "max": return args[0] > args[1] ? args[0] : args[1];
                    case "min": return args[0] < args[1] ? args[0] : args[1];
                    case "sgn":
                    case "sign":
                        return Math.Sign(args[0]);
                    case "rnd":
                    case "round":
                        return Math.Round(args[0], (int)args[1]);
                    case "fact":
                        return fact((int)args[0]);
                    case "A":
                        return fact((int)args[0]) / fact((int)(args[0] - args[1]));
                    case "C":
                        return fact((int)args[0]) / fact((int)args[1]) / fact((int)(args[0] - args[1]));
                    default:
                        return args[0];
                }
            }
        }
        static long fact(int num)
        {
            long result = 1;
            for (int i = 2; i <= num; i++)
            {
                result *= i;
            }
            return result;
        }
        public static char IsPriority(string stackPeek, string inputOp)
        {
            switch (stackPeek)
            {
                case "+":
                case "-":
                    switch (inputOp)
                    {
                        case "+":
                        case "-":
                        case ")":
                        case "#":
                            return '>';
                        case "*":
                        case "/":
                        case "(":
                        case "^":
                            return '<';
                    }
                    break;
                case "*":
                case "/":
                    switch (inputOp)
                    {
                        case "+":
                        case "-":
                        case "*":
                        case "/":
                        case ")":
                        case "#":
                            return '>';
                        case "(":
                        case "^":
                            return '<';
                    }
                    break;
                case "(":
                    switch (inputOp)
                    {
                        case "+":
                        case "-":
                        case "*":
                        case "/":
                        case "(":
                        case "^":
                            return '<';
                        case ")": return '=';
                        case "#": return '?';
                    }
                    break;
                case ")":
                    switch (inputOp)
                    {
                        case "+":
                        case "-":
                        case "*":
                        case "/":
                        case "^":
                            return '<';
                        case "(": return '?';
                        case ")":
                        case "#":
                            return '>';
                    }
                    break;
                case "#":
                    switch (inputOp)
                    {
                        case "+":
                        case "-":
                        case "*":
                        case "/":
                        case "(":
                        case "^":
                            return '<';
                        case ")": return '?';
                        case "#": return '=';
                    }
                    break;
                case "^":
                    switch (inputOp)
                    {
                        case "+":
                        case "-":
                        case "*":
                        case "/":
                        case ")":
                        case "#":
                        case "^":
                            return '>';
                        case "(":
                            return '<';
                    }
                    break;
            }
            return '?';
        }
        public static bool IsOperator(string exp)
        {
            switch (exp)
            {
                case "+":
                case "-":
                case "*":
                case "/":
                case "^":
                case "(":
                case ")":
                case "#":
                    return true;
                default:
                    return false;
            }
        }
    }
}
